﻿using System;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using O1.Kernel;
using O1.Kernel.Primitives;

namespace Tests.O1.Kernel.Primitives
{
    [TestClass]
    public class DateTimePropertyTests
    {
        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyWritesDataIntoPackedSizeBufferAsExpected()
        {
            var subject = new DateTimeProperty(6, new DateTime(2001, 1, 30));
            var buffer = new byte[DateTimeProperty.Size];
            subject.Write(buffer, 0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyWritesDataValuesAsExpected()
        {
            var value = new DateTime(2001, 1, 30);
            var subject = new DateTimeProperty(99, value);
            var buffer = new byte[DateTimeProperty.Size];
            subject.Write(buffer, 0);

            var actualValue = buffer.ToDateTimeOffset(0);
            var actualKey = BitConverter.ToInt32(buffer, Sz.CLR.DateTimeOffset);

            actualValue.Should().Be(value);
            actualKey.Should().Be(99);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyPreservesTimeZoneInfo()
        {
            var buffer = new byte[DateTimeProperty.Size];
            var value = new DateTimeOffset(new DateTime(2001, 1, 30), TimeSpan.FromHours(-5));

            value.CopyBytes(buffer, 0);
            var key = BitConverter.GetBytes(77);
            Array.Copy(key, 0, buffer, Sz.CLR.DateTimeOffset, 4);

            var subject = DateTimeProperty.Read(buffer, 0);
            subject.Key.Should().Be(77);
            subject.Value.Should().Be(value);
            subject.Value.Offset.Should().Be(TimeSpan.FromHours(-5));
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyReadsAsExpected()
        {
            var buffer = new byte[DateTimeProperty.Size];
            var value = new DateTimeOffset(new DateTime(2001, 1, 30), TimeSpan.Zero);

            value.CopyBytes(buffer, 0);
            var key = BitConverter.GetBytes(77);
            Array.Copy(key, 0, buffer, Sz.CLR.DateTimeOffset, 4);

            var subject = DateTimeProperty.Read(buffer, 0);
            subject.Key.Should().Be(77);
            subject.Value.Should().Be(value);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyWritesThenReadsAsExpected()
        {
            var expected = new DateTimeProperty(99, new DateTime(1999, 1, 20));
            var buffer = new byte[DateTimeProperty.Size];
            expected.Write(buffer, 0);

            var actual = DateTimeProperty.Read(buffer, 0);
            actual.Key.Should().Be(expected.Key);
            actual.Value.Should().Be(expected.Value);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyEqualityIsKeyValueEquality()
        {
            var subject1 = new DateTimeProperty(99, new DateTime(1999, 1, 20));
            var subject2 = new DateTimeProperty(99, new DateTime(1999, 1, 20));
            (subject1 == subject2).Should().BeTrue();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyInequalityIsKeyInequality()
        {
            var subject1 = new DateTimeProperty(99, new DateTime(1999, 1, 20));
            var subject2 = new DateTimeProperty(98, new DateTime(1999, 1, 20));
            (subject1 != subject2).Should().BeTrue();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyInequalityIsValueInequality()
        {
            var subject1 = new DateTimeProperty(98, new DateTime(1999, 1, 20));
            var subject2 = new DateTimeProperty(98, new DateTime(2009, 1, 20));
            (subject1 != subject2).Should().BeTrue();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyEqualsIsValueEquality()
        {
            var subject1 = new DateTimeProperty(99, new DateTime(1999, 1, 20));
            var subject2 = new DateTimeProperty(99, new DateTime(1999, 1, 20));
            subject1.Equals(subject2).Should().BeTrue();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyEqualsIsKeyInequality()
        {
            var subject1 = new DateTimeProperty(99, new DateTime(1999, 1, 20));
            var subject2 = new DateTimeProperty(98, new DateTime(1999, 1, 20));
            subject1.Equals(subject2).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyEqualsIsValueInequality()
        {
            var subject1 = new DateTimeProperty(98, new DateTime(1999, 1, 20));
            var subject2 = new DateTimeProperty(98, new DateTime(1999, 1, 10));
            subject1.Equals(subject2).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyIsNotEqualToNull()
        {
            object other = null;
            new DateTimeProperty(98, new DateTime(1999, 1, 20)).Equals(other).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyGetHashCodeIsConsistent()
        {
            var subject = new DateTimeProperty(98, new DateTime(1999, 1, 20));
            subject.GetHashCode().Should().Be(subject.GetHashCode());   
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyGetHashCodeIsDifferentForDifferentKey()
        {
            var subject1 = new DateTimeProperty(98, new DateTime(1999, 1, 20));
            var subject2 = new DateTimeProperty(101, new DateTime(1999, 1, 20));
            subject1.GetHashCode().Should().NotBe(subject2.GetHashCode());
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyGetHashCodeIsDifferentForDifferentValue()
        {
            var subject1 = new DateTimeProperty(101, new DateTime(1999, 1, 20));
            var subject2 = new DateTimeProperty(101, new DateTime(1989, 1, 20));
            subject1.GetHashCode().Should().NotBe(subject2.GetHashCode());
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyGetHashCodeIsDifferentForDifferentKeyAndValue()
        {
            var subject1 = new DateTimeProperty(88, new DateTime(1999, 1, 20));
            var subject2 = new DateTimeProperty(101, new DateTime(1999, 3, 20));
            subject1.GetHashCode().Should().NotBe(subject2.GetHashCode());
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyObjectEqualsIsEquality()
        {
            var subject1 = new DateTimeProperty(98, new DateTime(1999, 1, 20));
            object subject2 = new DateTimeProperty(98, new DateTime(1999, 1, 20));
            subject1.Equals(subject2).Should().BeTrue();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyObjectEqualsIsInequalityForDifferentKey()
        {
            var subject1 = new DateTimeProperty(99, new DateTime(1999, 1, 20));
            object subject2 = new DateTimeProperty(98, new DateTime(1999, 1, 20));
            subject1.Equals(subject2).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyObjectEqualsIsInequalityForDifferentValue()
        {
            var subject1 = new DateTimeProperty(99, new DateTime(1999, 1, 20));
            object subject2 = new DateTimeProperty(99, new DateTime(1999, 1, 22));
            subject1.Equals(subject2).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyObjectEqualsIsInequalityForDifferentKeyAndValue()
        {
            var subject1 = new DateTimeProperty(8769, new DateTime(1999, 1, 20));
            object subject2 = new DateTimeProperty(99, new DateTime(1999, 1, 21));
            subject1.Equals(subject2).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void DateTimePropertyObjectEqualsIsInequalityForDifferentType()
        {
            var subject1 = new DateTimeProperty(99, DateTime.UtcNow);
            object subject2 = new object();
            subject1.Equals(subject2).Should().BeFalse();
        }
    }
}
